﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;

namespace Devtm.ServiceModel.Discovery.Collection
{


    /// <summary>
    /// ViewCollection
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    public class ViewCollection<T, TKey> : TableCollection<T, TKey>
    {

        #region private

        private Monitoring _monitorFilter = new Monitoring();
        internal Expression<Func<T, bool>> ExpressionFilter;
        private Func<T, bool> _filter;
        private ITableCollection<T, TKey> list;

        #endregion

        #region ctor / ~

        internal ViewCollection(
            ITableCollection<T, TKey> list, 
            Expression<Func<T, bool>> filter
            )
            : base(list.FieldExpression)
        {

            this._filter = filter.Compile();
            this.ExpressionFilter = filter;
            this.list = list;

            list.CollectionChanged += list_CollectionChanged;
            list.PropertyItemChanged += list_PropertyItemChanged;

            foreach (T item in list)
                if (_filter(item))
                    base.Add(item);
            
        }


        public override void Dispose()
        {
            base.Dispose();

            list.CollectionChanged -= list_CollectionChanged;
            list.PropertyItemChanged -= list_PropertyItemChanged;
        }

        #endregion

        #region Events

        void list_PropertyItemChanged(object sender, ItemPropertyChangedEventArgs<T> e)
        {
            RefreshItem(e.Item);
        }


        private void RefreshItem(T item)
        {
            bool f = _filter(item);
            bool c = ContainsKeyFrom(item);

            if (c)
            {
                if (!f)
                    base.Remove(item);
            }

            else if (f)
                base.Add(item);
        }


        bool _test = false;
        /// <summary>
        /// Refresh the list if the filter have changed
        /// </summary>
        public void Refresh()
        {

            // regule l'entre du refresh
            // chaque nouveau refresh demande au precedent de se finir, car deja obsolete.
            _test = false;

            lock (list.SyncRoot())
            {

                _test = true;

                foreach (T item in list)
                {

                    if (!_test)
                        return;

                    RefreshItem(item);

                }
            }

        }



        void list_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {


            e.ApplyChange<T>
                (

                    item => // Add
                    {

                        if (!base.ContainsKeyFrom(item))
                        {

                            bool test = _filter(item);

                            if (test)
                                base.Add(item);
                        }
                    }

                    ,  // remove   
                    item => 
                    {

                        if (base.ContainsKeyFrom(item))
                        {

                            bool test = _filter(item);

                            if (!test)
                                base.Remove(item);

                        }
                    }
                    
                    ,  // Clear
                    () => Clear()               

                );

            //if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            //{

            //    foreach (var item in e.NewItems)
            //    {
            //        if (item is T)
            //        {
            //            if (_filter((T)item ))
            //                base.Add((T)item);
            //        }
            //        else
            //        {
            //            foreach (T item3 in item as IEnumerable<T>)
            //            {
            //                if (item3 is T)
            //                {
            //                    if (_filter(item3))
            //                        base.Add(item3);
            //                }
            //            }
            //        }
            //    }

            //}

            //else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            //{

            //    foreach (var item2 in e.OldItems)
            //    {
            //        if (item2 is T)
            //        {
            //            if (_filter((T)item2))
            //                base.Remove((T)item2);
            //        }
            //        else
            //        {
            //            foreach (T item3 in item2 as IEnumerable<T>)
            //            {
            //                if (item3 is T)
            //                {
            //                    if (_filter(item3))
            //                        base.Add(item3);
            //                }

            //            }
            //        }
            //    }

            //}


            //else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
            //    base.Clear();

        }

        #endregion

        #region Methods


        /// <summary>
        /// Adds the specified item to the the liste and return the item
        /// </summary>
        /// <param name="item">The item.</param>
        public override T Add(T item)
        {
            return list.Add(item);
        }


        /// <summary>
        /// Adds the range Item.
        /// </summary>
        /// <param name="items">The items.</param>
        public override void AddList(IList<T> items)
        {
            list.AddList(items);
        }


        /// <summary>
        /// Removes the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        public override void Remove(T item)
        {
            list.Remove(item);
        }


        /// <summary>
        /// Removes the item specified by the key and return the dettached element.
        /// </summary>
        /// <param name="key">The key.</param>
        public override void RemoveKey(TKey key)
        {
            list.RemoveKey(key);
        }


        /// <summary>
        /// Removes the item specified by the key and return the dettached element.
        /// </summary>
        /// <param name="key">The key.</param>
        public override bool TryRemoveKey(TKey key)
        {
            return list.TryRemoveKey(key);
        }


        /// <summary>
        /// Removes the item specified by the key and return the dettached element.
        /// </summary>
        /// <param name="key">The key.</param>
        public override bool TryRemove(T item)
        {
            return TryRemove(item);
        }


        /// <summary>
        /// Remplace l'élément à l'index spécifié.
        /// </summary>
        /// <param name="item">
        /// Nouvelle valeur de l'élément à l'index spécifié dans l'item
        /// </param>
        public override void Update(T item)
        {
            list.Update(item);
        }


        /// <summary>
        /// Supprime tous les éléments de la collection.
        /// </summary>
        public override void Clear()
        {
            foreach (var item in this)
                list.Remove(item);

        }

        #endregion

        #region Filter

        public Expression<Func<T, bool>> Filter
        {
            get
            {
                return ExpressionFilter;
            }
            set
            {

                CheckReentrancy();

                this._filter = value.Compile();
                this.ExpressionFilter = value;

                RefreshFilter();

            }
        }

        public void RefreshFilter()
        {

            CheckReentrancy();

            Action a =
                    () =>
                    {

                        using (this.BlockReentrancy())
                        {
                            foreach (T item in list)
                                RefreshItem(item);
                        }

                    };

            var t = new Thread(new ThreadStart(a));
            t.Start();

        }


        protected new IDisposable BlockReentrancy()
        {
            this._monitorFilter.Enter();
            return this._monitorFilter;
        }

        public new bool IsBusy { get { return _monitorFilter.Busy; } }

        /// <summary>
        /// Vérifie les tentatives réentrantes destinées à modifier cette collection.
        /// </summary>
        /// <exception cref="T:System.InvalidOperationException">
        /// S'il y a eu un appel à <see cref="M:System.Collections.ObjectModel.ObservableCollection`1.BlockReentrancy" /> dont la valeur de retour <see cref="T:System.IDisposable" /> n'a pas encore été éliminée. En général, cela signifie qu'il existe d'autres tentatives destinées à modifier cette collection pendant un événement <see cref="E:System.Collections.ObjectModel.ObservableCollection`1.CollectionChanged" />. Toutefois, cela dépend du moment où les classes dérivées choisissent d'appeler <see cref="M:System.Collections.ObjectModel.ObservableCollection`1.BlockReentrancy" />.
        /// </exception>
        protected new void CheckReentrancy()
        {
            if (this._monitorFilter.Busy)
                throw new InvalidOperationException("DualFilterReentrancyNotAllowed");
        }

        #endregion

    }
}
